package dados;

import corretor.Executador;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import logica.Utilitarios;

/**
 * Classe que guarda informa��es de um determinado arquivo fonte de um aluno.
 */
public class ArquivoFonte implements Serializable {    
	private static final long serialVersionUID = 1L;
	private boolean erroCompilacao = false;
    private File arquivo = null;
    private ArrayList<Saidas> saidas = null;
    private ArrayList<Double> notas = null;
    private ArrayList<Long> tempoExecucao = null;
    
    /**
     * Cria uma nova inst�ncia da classe ArquivoFonte.
     * @param arquivo O endere�o do arquivo fonte.
     */
    public ArquivoFonte(File arquivo) {
        this.arquivo = arquivo;
        notas = new ArrayList<Double>();
        saidas = new ArrayList<Saidas>();
        tempoExecucao = new ArrayList<Long>();
    }
    
    /**
     * Retorna o objeto File para o arquivo fonte.
     */
    public File getArquivo() {
        return arquivo;
    }
    
    /**
     * Retorna true se ocorreu erro de compila��o no arquivo e false caso contr�rio.
     */
    public boolean getErroCompilacao() {
        return erroCompilacao;
    }
    
    /**
     * Seta se ocorreu um erro de compila��o para este arquivo fonte.
     * @param erroCompilacao O valor booleano que diz se ocorreu um erro de compila��o.
     */
    public void setErroCompilacao(boolean erroCompilacao) {
        this.erroCompilacao = erroCompilacao;
        PastaCorrecao.getInstancia().setModificado(true);
    }
    
    /**
     * Retorna a lista de sa�das que tem o mesmo tamanho do n�mero de entradas
     * fornecidas.
     */
    public ArrayList<Saidas> getSaidas() {
        return saidas;
    }
    
    /**
     * Seta a lista de saidas para este arquivo fonte.
     */
    public void setSaidas(ArrayList<Saidas> saidas) {
        this.saidas = saidas;
        PastaCorrecao.getInstancia().setModificado(true);
    }
    
    /**
     * Retorna a nota de uma determinada entrada para este arquivo fonte.
     * @param indice O �ndice da nota que se quer ter acesso.
     */
    public double getPorcentagem(int indice) {
        return notas.get(indice).doubleValue();
    }
    
    public ArrayList<Double> getPorcentagens() {
        return notas;
    }
        
    /**
     * Adiciona uma nota para este arquivo fonte. Lembrando que o �ndice da nota
     * est� ligado diretamente � ordem das entradas fornecidas.
     */    
    public void adicionarPorcentagem(double nota) {
        notas.add(new Double(nota));
    }
    
    /**
     * Retorna a nota total deste arquivo fonte que � a soma das notas dividida
     * pelo n�mero delas. 
     */
    public double getPorcentagemTotal() {
        double somaNotas = 0;
        int nroNotas = notas.size();
        for (int i = 0; i <= nroNotas - 1; i++) {
            somaNotas += notas.get(i).doubleValue();
        }
        return somaNotas/nroNotas;
    }
    
    /**
     * Corrige o arquivo fonte para uma determinada entrada fornecida.
     * @return A sa�da que o programa gerou para a entrada em forma de String.
     */
    public String corrigir(String entrada) {
        String saida = null;
        
/*FUNCIONA WINDOWS!
        String[] args = new String[] { "cmd.exe", "/C",
            arquivo.getName().substring(0, arquivo.getName().length() - 4) + ".exe" };
        Executador ex = new Executador(arquivo.getParentFile(), args, entrada);
*/
        String[] args = new String[] { "./" + arquivo.getName().substring(0, arquivo.getName().length() - 2) };
            Executador ex = new Executador(arquivo.getParentFile(), args, entrada);
        ex.executar();
        tempoExecucao.add(ex.getTempoExecucao());
        saida = ex.getSaida();
        
        return saida;
    }
    
    /*Se tem struct retorna 1, senão 0
     * verifica se a palavra struct não foi impressa em um printf*/
    public Boolean usouStruct(){
    	BufferedReader br = null;
    	File aux = this.arquivo;
    	String linha;
    	
    	try {
			br = new BufferedReader(new FileReader(aux));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		try {
			while( (linha = br.readLine()) != null ){
				if(linha.contains("struct") && !linha.contains("printf")){
					return true;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return false;
    }
    
    public int usouFuncao(){
    	BufferedReader br = null;
		String linha;
		int flag = 0;
		int comecou = 0;
		File aux = this.arquivo;
		
		try {
			br = new BufferedReader(new FileReader(aux));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		try {
			while( (linha = br.readLine()) != null ){
				if(linha.contains("#") && linha.contains("include")){
					comecou = 1;
				}
				if(comecou == 1){
					if(linha.contains("{") && !linha.contains("if") && 
							                  !linha.contains("for") && 
							                  !linha.contains("while") &&
							                  !linha.contains("switch") && 
							                  !linha.contains("printf")){
						flag++;
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		if(flag >= 2){
			return 1;
		}
		return 0;
    }
    
    public Boolean condicionais(int nesp){
    	BufferedReader br = null;
		String linha;
		int nelse = 0;
		File aux = this.arquivo;
		
		try {
			br = new BufferedReader(new FileReader(aux));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		try {
			while( (linha = br.readLine()) != null ){
				if(linha.contains("else")){
					nelse++;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		if(nelse == nesp){
			return true;
		}
		return false;
    }
    
    /**
     * Testa se a saida e o gabarito informados s�o equivalentes.
     * @param saida A saida que se deseja testar com o gabarito.
     * @param gabarito O gabarito para se testar a saida.
     */
    public String[] testarGabaritoA(Teste teste, String saida) {
        ArrayList<LinhaGabarito> linhasGabarito = teste.getLinhasGabarito();
        ModeloLinhaGabarito modeloLinhaGabarito = teste.getModeloLinhaGabarito();
        String[] relatorio = new String[2];
        String[] linhasSaida = null;
        int limiteLinhas = 0;
        float nroAcertos = 0;
        float pct = 0;
        int nota = 0;
        ArrayList<Integer> colunasErradas = new ArrayList<Integer>();
        
        relatorio[0] = "";
        
        int res = saida.indexOf("Runtime error");
        if(res >= 0){
        	relatorio[0] += "Erro de execução: \n" + saida + "\n";
        	relatorio[0] += "\nNota (0 ou 100)%: " + 0 + "\n";
            this.adicionarPorcentagem(0);
            PastaCorrecao.getInstancia().setModificado(true);
            return relatorio;
        }
        
        linhasSaida = saida.split("\n");
        
        limiteLinhas = linhasGabarito.size();
        if (linhasSaida.length < linhasGabarito.size()) {
            limiteLinhas = linhasSaida.length;
        }
        
        for (int i = 0; i <= limiteLinhas - 1; i++) {
            Boolean erro = false;
        	int limiteColunas = 0;
            String[] colunasLinha = null;
            
            colunasErradas.clear();
            colunasLinha = linhasSaida[i].split(" ");
            colunasLinha = Utilitarios.retirarElementosNulos(colunasLinha);
            
            limiteColunas = modeloLinhaGabarito.getNroColunas();
            
            //Testa se a linha de saida do aluno possui o mesmo numero de variaveis que a do gabarito
            if (colunasLinha.length != modeloLinhaGabarito.getNroColunas() && colunasLinha.length < modeloLinhaGabarito.getNroColunas()) {
                limiteColunas = colunasLinha.length;
                erro = true;
            }
        	for (int j = 0; j <= limiteColunas - 1; j++) {
                String tipo = modeloLinhaGabarito.getColuna(j);
                String aux = linhasGabarito.get(i).getColuna(j);
                
                if (tipo.equals("String")) { //String
                	if (!Utilitarios.compararStrings(colunasLinha[j], getColunaSemEspacos(aux))){
                        colunasErradas.add(new Integer(j));
                    }
                } else if (tipo.equals("Número")) { //Real
                    if (!Utilitarios.compararReais(colunasLinha[j], getColunaSemEspacos(aux))) {
                        colunasErradas.add(new Integer(j));
                    }
                }
            }
            
            if(i <= linhasGabarito.size()){
            	relatorio[1] = imprime(i, limiteColunas, linhasGabarito);
	            if (colunasErradas.isEmpty() && limiteColunas > 0 && !erro) {
	                relatorio[0] += "Acertou a linha " + (i + 1) + "\n" + "          Gabarito: (" +
	                        relatorio[1] + ")\n          Saída do Aluno: (" + linhasSaida[i] + ")\n";
	                nroAcertos += 1;
	            } else if(colunasErradas.size() >= limiteColunas){
	                relatorio[0] += "Errou a linha " + (i + 1) + "\n" + "          Gabarito: (" + 
	                        relatorio[1] + ")\n          Saída do Aluno: (" + linhasSaida[i] + ")\n";
	            } else if(erro) {
	            	int cErr = modeloLinhaGabarito.getNroColunas() - (limiteColunas - colunasErradas.size());
	            	//System.out.println("CERR:"+cErr);
	            	pct = 1 - ((float)cErr/(modeloLinhaGabarito.getNroColunas()));
	            	relatorio[0] += "Acertou parcialmente ("+(int)Math.ceil(pct*100)+"%) a linha " + (i + 1) + "\n" + 
	                "          Gabarito: (" +
	                relatorio[1] +
	                ")\n          Saída do Aluno: (" + linhasSaida[i] + ")\n";
	            	nroAcertos += pct;
	            	
	            } else {
	            	pct = 1 - ((float)colunasErradas.size()/(limiteColunas));
	            	relatorio[0] += "Acertou parcialmente ("+(int)Math.ceil(pct*100)+"%) a linha " + (i + 1) + "\n" + 
	                "          Gabarito: (" + 
	                relatorio[1] +
//	                imprime(i, limiteColunas, linhasGabarito) +
//	                linhasGabarito.get(i).getLinhaString() +
	                ")\n          Saída do Aluno: (" + linhasSaida[i] + ")\n";
	            	nroAcertos += pct;
	            } 
            } else {
            	relatorio[0] += "Linha " +i+ " não existente no gabarito\n";
            }
        }        
               
        if(linhasGabarito.size() != 0){
        	double notas = Math.ceil(((float)nroAcertos/linhasGabarito.size()*100));
        	nota = (int)notas;
        }
        
        relatorio[0] += "\nNota (0 ou 100)%: " + nota + "\n";
        this.adicionarPorcentagem(nota);
        PastaCorrecao.getInstancia().setModificado(true);
        return relatorio;
    }
    
    public String imprime(int i, int limiteColunas, ArrayList<LinhaGabarito> linhasGabarito){
    	String linhax = "";
    	for (int j = 0; j <= limiteColunas - 1; j++) {
    		linhax += linhasGabarito.get(i).getColuna(j);
    		if(j != limiteColunas -1)
    			linhax += " ";
    	}
    	return linhax;
    }
    
    public String[] testarGabaritoB(Teste teste, String saida) {
    	System.out.println("TIPOB");
        ArrayList<LinhaGabarito> linhasGabarito = teste.getLinhasGabarito();
        //System.out.println(linhasGabarito.get(0).getLinha().toString());
        String[] relatorio = new String[2];
        relatorio[0] = "";
        relatorio[1] = imprime(0, 1, linhasGabarito);
        
        double porcentagemNota = Utilitarios.compararCharDeStrings(saida, linhasGabarito
                .get(0).getColuna(0));
        relatorio[0] += "Questao tipo B \n" +
        				"Gabarito: (" + relatorio[1] + 
//        linhasGabarito.get(0).getLinhaString() +
                ")\n          Saída do Aluno: (" + saida + ")\n\nNota(0 a 100)%: " +
                porcentagemNota + "\n";
        this.adicionarPorcentagem(porcentagemNota);
        PastaCorrecao.getInstancia().setModificado(true);
        return relatorio;
    }
    
    public void addPeso(){
    	this.adicionarPorcentagem(0);
    }
    
    /**
     * Retorna o tempo de execu��o de acordo com o n�mero da entrada.
     * @param indice N�mero da entrada para o arquivo fonte que gerou o tempo.
     */
    public long getTempoExecucao(int indice) {
        return tempoExecucao.get(indice);
    }
        
    /**
     * Reinicia a contagem das notas deste arquivo fonte. Lembrando que para
     * cada entrada/gabarito o arquivo fonte tem uma nota.
     */
    public void reiniciarContagem() {
        saidas.clear();
        notas.clear();
        tempoExecucao.clear();
        PastaCorrecao.getInstancia().setModificado(true);
    }
    
    public String getColunaSemEspacos(String linha) {
    	int tam = linha.length();
    	String aux, aux2;
    	int i = 0;
    	for(i=0; i <= tam - 1; i++){
			if(linha.charAt(i) == ' '){
				aux = linha.substring(0, i);
				aux2 = linha.substring(i+1, linha.length());
				aux += aux2;
				linha = aux;
				i--;
				tam = linha.length();
			}
    	}
        return linha;
    }
    
}